package com.googlecode.kipler.satisfiability.completion.rule.individual;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import com.googlecode.kipler.container.dl.ABox;
import com.googlecode.kipler.container.dl.DependencySet;
import com.googlecode.kipler.container.dl.Individual;
import com.googlecode.kipler.container.dl.SHIQABox;
import com.googlecode.kipler.container.dl.WcsData;
import com.googlecode.kipler.container.dl.operational.SHIQAncestorCollector;
import com.googlecode.kipler.satisfiability.reasoner.UnsatisfiableException;
import com.googlecode.kipler.syntax.Role;
import com.googlecode.kipler.syntax.formula.SameIndividualAssertion;


/**
 * 
 * @author İnanç Seylan
 * 
 */
public class SHIQABoxNotBothRootMergeStrategy implements MergeStrategy {

	private String owner;

	private SHIQABox aBox;

	private DependencySet dependencySet;

	private String getOwner() {
		return owner;
	}

	private void setOwner(String owner) {
		this.owner = owner;
	}

	private SHIQABox getABox() {
		return aBox;
	}

	private void setABox(ABox box) {
		aBox = (SHIQABox) box;
	}

	private DependencySet getDependencySet() {
		return dependencySet;
	}

	private void setDependencySet(DependencySet dependedWcs) {
		this.dependencySet = dependedWcs;
	}

	public MergePair choosePair(ABox aBox, Set<Individual> neighbors) {
		setABox(aBox);
		MergePair result = MergePair.emptyPair();
		Iterator<Individual> i = neighbors.iterator();
		while (i.hasNext() && result.equals(MergePair.emptyPair())) {
			Individual indv1 = i.next();
			Iterator<Individual> j = neighbors.iterator();
			while (j.hasNext() && result.equals(MergePair.emptyPair())) {
				Individual indv2 = j.next();
				SameIndividualAssertion ia = new SameIndividualAssertion(indv1
						.getName(), indv2.getName()).toggleNegated();
				if (indv1 != indv2
						&& !getABox().contains(ia)
						&& !(getABox().isRoot(indv1) && getABox().isRoot(indv2))) {
					if (getABox().isRoot(indv1)) {
						result = new MergePair(indv2.getName(), indv1.getName());
					} else if (getABox().isRoot(indv2)
							|| new SHIQAncestorCollector(getABox(), indv1)
									.collect().contains(indv2)) {
						result = new MergePair(indv1.getName(), indv2.getName());
					} else {
						result = new MergePair(indv2.getName(), indv1.getName());
					}
				}
			}
		}
		return result;
	}

	public void merge(String owner, ABox aBox, MergePair pair,
			DependencySet dependencySet) throws UnsatisfiableException {
		setOwner(owner);
		setABox(aBox);
		setDependencySet(dependencySet);
		Individual node = getABox().getIndividual(pair.getFrom());
		Individual into = getABox().getIndividual(pair.getInto());
		MergeUtility mergeUtility = new MergeUtility(getABox(),
				getDependencySet());
		mergeUtility.mergeConcepts(node.getName(), into.getName());
		mergeRoleAssertions(node, into);
		mergeUtility.mergeInequalities(node.getName(), into.getName());
		replaceOccurenceOfMergedNodeInModifiedIndividuals(pair);
		prune(node.getName());
	}

	private void replaceOccurenceOfMergedNodeInModifiedIndividuals(
			MergePair pair) {
		WcsData wcsData = getABox().getWorld().getWcs().getData();
		wcsData.getModifiedIndividuals().replace(
				getABox().getWorld().getName(), pair.getFrom(), pair.getInto());
	}

	/**
	 * @param newABox
	 * @param y
	 * @param z
	 * @throws UnsatisfiableException
	 */
	private void mergeRoleAssertions(Individual y, Individual z)
			throws UnsatisfiableException {
		Individual x = getABox().getIndividual(getOwner());
		for (Role edge : getABox().getEdges(x, y)) {
			Role role;
			Individual from, to;
			// z is an ancestor of x
			if (getABox().containsEdge(z, x)) {
				role = edge.copy().toggleInverse();
				from = z;
				to = x;
			} else {
				role = edge;
				from = x;
				to = z;
			}
			/*
			 * We have to use "getEdges" method here because "contains" method
			 * for object role assertions considers role hierarchies and this
			 * could result in edges not being added. For example, if P was a
			 * subproperty of R and P(a, b) was in the ABox, then contains would
			 * return true for R(a, b) and the new edge with R wouldn't be
			 * added.
			 */
			new MergeUtility(getABox(), getDependencySet()).mergeEdge(edge, x,
					y, role, from, to);
		}
	}

	private void removeDeletedNodeFromModifiedIndividuals(String indvName) {
		WcsData wcsData = getABox().getWorld().getWcs().getData();
		wcsData.getModifiedIndividuals().remove(getABox().getWorld().getName(),
				indvName);
	}

	private void prune(String indvName) {
		Set<Individual> toBePrunedSet = new HashSet<Individual>();
		collectPruneNodes(indvName, toBePrunedSet);
		for (Individual indv : toBePrunedSet) {
			getABox().remove(indv.getName());
			removeDeletedNodeFromModifiedIndividuals(indv.getName());
			/*
			 * There is no need to manage dependencies when an individual is
			 * removed from the ABox because we don't use the same new object
			 * name for different individuals in alternative (produced by
			 * disjunctive rules) ABoxes.
			 */
		}
	}

	private void collectPruneNodes(String indvName,
			Set<Individual> toBePrunedSet) {
		Individual indv = getABox().getIndividual(indvName);
		toBePrunedSet.add(indv);
		Iterator<Individual> i = aBox.successorIterator(indv);
		while (i.hasNext()) {
			/*
			 * there is no need to check if the successor is a root node because
			 * a non-root node can never be the ancestor of a root node.
			 */
			Individual successor = i.next();
			collectPruneNodes(successor.getName(), toBePrunedSet);
		}
	}
}
